/*-----------------------------------------------------------------------------
	The contents of this file are subject to the Mozilla Public License
	Version 1.1 (the "License"); you may not use this file except in
	compliance with the License. You may obtain a copy of the License at
	http://www.mozilla.org/MPL/
	
	Software distributed under the License is distributed on an "AS IS"
	basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
	License for the specific language governing rights and limitations
	under the License.
	
	The Original Code is Fever Framework code.
	
	The Initial Developer of the Original Code is Romain Ecarnot.
	Portions created by Initial Developer are Copyright (C) 2006
	the Initial Developer. All Rights Reserved.
	
	Contributor(s):
	
	Unless required by applicable law or agreed to in writing, software
	distributed under the License is distributed on an "AS IS" BASIS,
	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
	See the License for the specific language governing permissions and
	limitations under the License.
-----------------------------------------------------------------------------*/

import org.aswing.ASColor;
import org.aswing.ASFont;
import org.aswing.ASTextFormat;
import org.aswing.border.EmptyBorder;
import org.aswing.border.LineBorder;
import org.aswing.BorderLayout;
import org.aswing.CenterLayout;
import org.aswing.Component;
import org.aswing.Event;
import org.aswing.GridLayout;
import org.aswing.Insets;
import org.aswing.JButton;
import org.aswing.JLabel;
import org.aswing.JPanel;
import org.aswing.JSlider;
import org.aswing.SoftBox;
import org.aswing.SoftBoxLayout;
import org.aswing.UIManager;
import org.aswing.util.ArrayUtils;
import org.aswing.util.Delegate;

import com.bourre.data.collections.Map;
import com.bourre.events.EventType;

import fever.app.local.LocalisationEvent;
import fever.app.local.LocalisationListener;
import fever.utils.ArrayUtil;
import fever.utils.DateResources;
import fever.utils.DateUtils;
import fever.utils.Stringifier;

import fvaswing.components.datechooser.FvCalendarResources;
import fvaswing.components.datechooser.FvDayCell;
import fvaswing.components.datechooser.FvLockDay;
import fvaswing.components.datechooser.FvNavigationIcon;
import fvaswing.components.FvInputComponentDialog;
import fvaswing.components.FvLabelButton;
import fvaswing.components.input.FvComboInput;
import fvaswing.components.input.FvSliderInput;

/**
 * Calendar component.
 * 
 * <p>Example :
 * {@code
 *  var calendar : FvCalendar = new FvCalendar();
 *  calendar.setMultipleSelection( true );
 *  calendar.setMinimumYear( 2000 );
 *  calendar.setMaximumYear( 2010 );
 *  
 *  var panel : JPanel = new JPane( new BorderLayout( 5, 5 ) );
 *  panel.append( calendar, BorderLayout.CENTER ); 
 * }
 * 
 * @author Romain Ecarnot
 */
class fvaswing.components.FvCalendar extends JPanel implements LocalisationListener
{
	//-------------------------------------------------------------------------
	// Events definition
	//-------------------------------------------------------------------------
	
	/** 
	 * Broadcasted each time the selection change.
	 * 
	 * <p>Depending of {@link isMultiSelectionEnabled()} result, use 
	 * {@link #getSelectedDate()} or {@link #getSelectedDates()} method to retreive 
	 * selected date(s).
	 */
	public static var onCalendarSelectionEVENT : EventType = new EventType( "onCalendarSelection" );
	
	/** Broadcasted when day cell receive a click. */
	public static var onCalendarClickEVENT : EventType =  new EventType( "onCalendarClick" );
	
	/** Broadcasted when calendar view ( current month and year ) change. */
	public static var onCalendarChangeViewEVENT : EventType = new EventType( 'onCalendarChangeView' );
	
	
	//-------------------------------------------------------------------------
	// Private properties
	//-------------------------------------------------------------------------
	
	private static var _MIN_YEAR : Number = 1900;
	private static var _MIN_MONTH : Number = 0;
	
	private static var _lockedDays : Map;
	
	private var _navigationPanel : JPanel;	private var _daysContainer : JPanel;
	private var _fwdButton : JButton;
	private var _backButton : JButton;
	private var _monthLabel : FvLabelButton;	private var _yearLabel : FvLabelButton;
	
	private var _dayNames : Array;
	private var _monthNames : Array;
	
	private var _dayNamePanel : JPanel;
	private var _dayGrid : JPanel;
	private var _gridLength : Number;
	
	private var _firstDayOfWeek : Number;
	private var _daysLabelFormat : ASTextFormat;
	
	private var _maxYear : Number;
	private var _minYear : Number;
	private var _minMonth : Number;
	private var _multiSelection : Boolean;	private var _showToday : Boolean;
	
	private var _currentYear : Number;
	private var _currentMonth : Number;
	private var _yearNavigation : Boolean;
	private var _storedSelection : Object;
	
	private var _inputMonthComponent : FvComboInput;
	private var _inputYearComponent : FvSliderInput;
	
	private var _enablePastNavigation : Boolean;
	private var _enableWeekEnd : Boolean;
	
	private var _enableLockDays : Boolean;
	private var _disabledDays : Array;
	
	private var _enabledYearNavigation : Boolean;
	private var _enabledMonthNavigation : Boolean;
	
		
	//-------------------------------------------------------------------------
	// Public API
	//-------------------------------------------------------------------------
	
	/**
	 * 
	 */
	public static function addLockedDaysForYear( year : Number, dayList : Array ) : Void
	{
		if( !_lockedDays ) _lockedDays = new Map();
		
		_lockedDays.put( year, dayList );
	}
	
	/**
	 * Constructor.
	 */
	public function FvCalendar()
	{
		super( new BorderLayout( 5, 5 ) );
		
		_firstDayOfWeek = 1;
		_maxYear = ( new Date() ).getFullYear() + 100;
		_minYear = _MIN_YEAR;
		_minMonth = _MIN_MONTH;
		_multiSelection = false;
		_showToday = true;
		_yearNavigation = false;
		_enablePastNavigation = false;
		_enableWeekEnd = true;
		_enableLockDays = false;
		_enabledYearNavigation = true;
		_enabledMonthNavigation = true;
		
		_storedSelection = new Object();
		
		_configureUI();
		
		_init();
	}
	
	/**
	 * Sets max year value.
	 */
	public function setMaximumYear( year : Number ) : Void
	{
		if( year > _minYear && year >= _currentYear ) _maxYear = year;
	}
	
	/**
	 * Returns max year value.
	 */
	public function getMaximumYear() : Number
	{
		return _maxYear;	
	}
	
	/**
	 * Sets min year value.
	 */
	public function setMinimumYear( year : Number ) : Void
	{
		if( _enablePastNavigation )
			if( year < _maxYear && year < _currentYear ) _minYear = year;
		else _minYear = DateUtils.getCurrentYear();
	}
	
	/**
	 * Returns min year value.
	 */
	public function getMinimumYear() : Number
	{
		return _minYear;	
	}
	
	/**
	 * Indicates if component highlight 'today' cell.<br />
	 * Default is {@code true}
	 */
	public function setShowTodayEnabled( b : Boolean ) : Void
	{
		if( b != _showToday )
		{
			_showToday = b;
		}	
	}
	
	/**
	 * Returns {@code true} if calendar highlight the 'today' cell.
	 */
	public function isShowTodayEnabled() : Boolean
	{
		return _showToday;	
	}
	
	/**
	 * Sets the first day ( index ) of a week.<br />
	 * Default is 
	 */
	public function setFirstDayOfTheWeek( n : Number ) : Void
	{
		if( ( n != _firstDayOfWeek ) && ( n > -1 ) && ( n < 7 ) )
		{
			_firstDayOfWeek = n;
			_updateCalendar();	
		}
	}
	
	/**
	 * Sets if multiple date selection is enabled or not.<br />
	 * Default is {@code false}.
	 * 
	 * <p>Use {@link #getSelectedDates()} to retrieves all selected date if 
	 * multiple selection is allowed.<br />
	 * Otherwise use {@link #getSelectedDate()} method for single selection.
	 */
	public function setMultiSelectionEnabled( b : Boolean ) : Void
	{
		_multiSelection = b;
	}
	
	/**
	 * Returns {@code true} if multiple date selection is enabled.
	 * 
	 * <p>Use {@link #getSelectedDates()} to retrieves all selected date if 
	 * multiple selection is allowed.<br />
	 * Otherwise use {@link #getSelectedDate()} method for single selection.
	 */
	public function isMultiSelectionEnabled() : Boolean
	{
		return 	_multiSelection;
	}
	
	/**
	 * Indicates if past navigation is allowed or not.
	 * 
	 * @param b <code>true</code> to allow past navigation
	 */
	public function setPastNavigationEnabled( b : Boolean ) : Void
	{
		if( !b )
		{
			_minYear = DateUtils.getCurrentYear();
			_minMonth = DateUtils.getCurrentMonth();
		}
		else
		{
			_minYear = _MIN_YEAR;
			_minMonth = _MIN_MONTH;	
		}
		
		_enablePastNavigation = b;
		
		_updateCalendar();
	}
	
	/**
	 * Returns <code>true> if past navigation is allowed.
	 * 
	 * <p>Default is <code>false</code>
	 */
	public function isPastNavigationEnabled(  ) : Boolean
	{
		return _enablePastNavigation;
	}
	
	/**
	 * Indicates if weekend days can be selected.
	 * 
	 * @param b <code>true</code> to allow weekend selection
	 */
	public function setWeekEndEnabled( b : Boolean ) : Void
	{
		if( _enableWeekEnd != b )
		{
			_enableWeekEnd = b;
			_updateCalendar();
		}
	}
	
	/**
	 * Returns <code>true> if weekend can be selected.
	 * 
	 * <p>Default is <code>true</code>
	 */
	public function isWeekEndEnabled(  ) : Boolean
	{
		return _enableWeekEnd;
	}
	
	/**
	 * Indicates if lock days are managed.
	 * 
	 * @param b <code>true</code> to allow lock days view
	 */
	public function setLockDaysEnabled( b : Boolean ) : Void
	{
		if( b != _enableLockDays )
		{
			_enableLockDays = b;
			_updateCalendar();
		}
	}
	
	/**
	 * Returns <code>true> if lock days are enabled.
	 * 
	 * <p>Default is <code>false</code>
	 */
	public function isLockDaysEnabled(  ) : Boolean
	{
		return _enableLockDays;
	}
	
	/**
	 * Defines disabled days for current view.
	 */
	public function setDisabledDays( list : Array ) : Void
	{
		_disabledDays = list.concat();
		_updateCalendar();
	}
	
	/**
	 * Sets selected date.
	 */
	public function setSelectedDate( date : Date ) : Void
	{
		if( _isValidDate( date ) ) 
		{
			_currentMonth = date.getMonth();
			_currentYear = date.getFullYear();
			
			_storedSelection = date.getDate();
			_updateCalendar();
		}
	}
	
	/**
	 * Returns selected date.
	 * 
	 * <p>If {@link #isMultiSelectionEnabled()} is {@code true} return {@code null}.
	 */
	public function getSelectedDate() : Date
	{
		if( _multiSelection ) return null;
		else return  _buildDateObject( _storedSelection );  	
	}
	
	/**
	 * Returns selected dates.
	 * 
	 * <p>If {@link #isMultiSelectionEnabled()} is {@code false} return {@code null}.
	 */
	public function getSelectedDates() : Array
	{
		if( !_multiSelection ) return null;
		else return  _buildDateRangeObject( _storedSelection );  	
	}
	
	/**
	 * Returns current displayed month.
	 * 
	 * @return Number
	 */
	public function getCurrentMonth(  ) : Number
	{
		return _currentMonth;
	}
	
	/**
	 * Returns current displayed year.
	 * 
	 * @return Number
	 */
	public function getCurrentYear(  ) : Number
	{
		return _currentYear;
	}
	
	/**
	 * Indicates if year navigation is allowed ( <code>true</code> ) or not.
	 */
	public function setYearNavigationEnabled( b : Boolean ) : Void
	{
		_enabledYearNavigation = b;
		
	}
	
	/**
	 * Returns <code>true</code> if year navigation is enabled.
	 */
	public function isYearNavigationEnabled(  ) : Boolean
	{
		return _enabledYearNavigation;	
	}
	
	/**
	 * Indicates if month navigation is allowed ( <code>true</code> ) or not.
	 */
	public function setMonthNavigationEnabled( b : Boolean ) : Void
	{
		_enabledMonthNavigation = b;
		
	}
	
	/**
	 * Returns <code>true</code> if year month is enabled.
	 */
	public function isMonthNavigationEnabled(  ) : Boolean
	{
		return _enabledMonthNavigation;	
	}
	
	/**
	 * Changes calendar navigation mode to passed-in <code>mode</code>.
	 * 
	 * @param mode Boolean
	 */
	public function setNavigationMode( yearMode : Boolean ) : Void
	{
		_yearNavigation = yearMode;	
		
		if( _yearNavigation ) _yearLabel.click();
		else _monthLabel.click();
	}
	
	/**
	 * Triggered when Localisation language change.
	 */
	public function onLocalisationUpdate(event : LocalisationEvent ) : Void
	{
		_dayNames = DateResources.getInstance().fullDays;
		_monthNames = DateResources.getInstance().fullMonths;
		
		_monthLabel.setToolTipText( FvCalendarResources.getInstance().monthNavigation );		_yearLabel.setToolTipText( FvCalendarResources.getInstance().yearNavigation );
		
		_fillDayNamesPanel();
	}
	
	/**
	 * Returns string representation.
	 */
	public function toString() : String
	{
		return Stringifier.parse( this );
	}
	
	
	//-------------------------------------------------------------------------
	// Private implementation
	//-------------------------------------------------------------------------
	
	private function _configureUI() : Void
	{
		_daysLabelFormat = ASTextFormat.getASTextFormatWithASFont( UIManager.getFont( 'systemFont' ) );
		_daysLabelFormat.setBold( true );
		_daysLabelFormat.setSize( _daysLabelFormat.getSize() + 1 );		
		append( _createNavigation(), BorderLayout.NORTH );
		append( _createDayPanel(), BorderLayout.CENTER );
		
		addEventListener( Component.ON_PAINT, _updateCalendarUI, this );
	}
	
	private function _init() : Void
	{
		_currentYear = DateUtils.getCurrentYear();
		_currentMonth = DateUtils.getCurrentMonth();
		
		_updateCalendar();
	}
	
	/**
	 * Force repainting only if background color change.<br />
	 * Background change when new Look'n Feel is apply.
	 */
	private function _updateCalendarUI() : Void
	{
		if( _daysContainer.getBackground() != UIManager.getColor( 'List.background' ) )
		{
			var n : Number = _getNumberOfDaysInMonth( _currentYear, _currentMonth );
			var offset : Number = _getOffsetOfMonth( _currentYear, _currentMonth );
			
			_daysContainer.setBackground( UIManager.getColor( 'List.background' ) );
			_monthLabel.setHightlightColor( UIManager.getColor( 'List.selectionBackground' ) );
			_yearLabel.setHightlightColor( UIManager.getColor( 'List.selectionBackground' ) );
			
			_inputMonthComponent.getContainer().repaint();			_inputYearComponent.getContainer().repaint();
			
			_fillDayCellGrid( n, offset );
		}
	}
	
	private function _updateCalendar() : Void
    {
    	var n : Number = _getNumberOfDaysInMonth( _currentYear, _currentMonth );
		var offset : Number = _getOffsetOfMonth( _currentYear, _currentMonth );
		
		onLocalisationUpdate();
		
		_evaluateNavigationItem();
		
		_yearLabel.setText( _currentYear.toString() );
		
		_fillDayCellGrid( n, offset );
		
		_fireEvent( onCalendarSelectionEVENT );
    }
    
	private function _createNavigation(): JPanel
	{
		_inputMonthComponent = new FvComboInput();		_inputYearComponent = new FvSliderInput( JSlider.HORIZONTAL, true );
		
		_fwdButton = new JButton( new FvNavigationIcon( 12 ) );
		_fwdButton.setPreferredSize( 25, 25 );
		_fwdButton.addActionListener( _handleNextClick, this );
		
		_backButton = new JButton( new FvNavigationIcon( 12, -Math.PI ) );
		_backButton.setPreferredSize( 25, 25 );
		_backButton.addActionListener( _handlePreviousClick, this );
		
		_monthLabel = new FvLabelButton( "-", JLabel.RIGHT );
		_monthLabel.setFont( ASFont.getASFontFromASTextFormat( _daysLabelFormat ) );
		_monthLabel.setUseHandCursor( false );
		_monthLabel.setNormalColor( UIManager.getColor( 'Button.foreground' ) );
		_monthLabel.setHightlightColor( UIManager.getColor( 'List.selectionBackground' ) );
		_monthLabel.setToolTipText( FvCalendarResources.getInstance().monthNavigation );
		_monthLabel.addEventListener( FvLabelButton.ON_CLICKED, Delegate.create( this, _handleNavigationType, false ) );
		
		_yearLabel = new FvLabelButton( "-", JLabel.LEFT );
		_yearLabel.setFont( ASFont.getASFontFromASTextFormat( _daysLabelFormat ) );
		_yearLabel.setUseHandCursor( false );
		_yearLabel.setNormalColor( UIManager.getColor( 'Button.foreground' ) );
		_yearLabel.setHightlightColor( UIManager.getColor( 'List.selectionBackground' ) );
		_yearLabel.setToolTipText( FvCalendarResources.getInstance().yearNavigation );
		_yearLabel.addEventListener( FvLabelButton.ON_CLICKED, Delegate.create( this, _handleNavigationType, true ) );
		
		var line : JPanel = new SoftBox( SoftBoxLayout.X_AXIS, 5, SoftBoxLayout.LEFT );
		line.append( _monthLabel );
		line.append( _yearLabel );
		var center : JPanel = new JPanel( new CenterLayout() );
		center.append( line );
		
		_navigationPanel = new JPanel( new BorderLayout( 5,5 ) );
		_navigationPanel.setBorder( new EmptyBorder( null, new Insets( 2,2,2,2 ) ) );
		_navigationPanel.append( _backButton, BorderLayout.WEST );
		_navigationPanel.append( center, BorderLayout.CENTER );
		_navigationPanel.append( _fwdButton, BorderLayout.EAST );
		
		return _navigationPanel;
	}
	
	private function _createDayPanel() : JPanel
	{
		_daysContainer = new JPanel( new BorderLayout( 5, 5 ) );
		_daysContainer.setOpaque( true );
		_daysContainer.setBackground( UIManager.getColor( 'List.background' ) );
		
		_daysContainer.append( _createDayNamesPanel(), BorderLayout.NORTH );
		_daysContainer.append( _createDayGrid(), BorderLayout.CENTER );
		
		_daysContainer.setPreferredSize( 160, 160 );
		return _daysContainer;	
	}
	
	private function _createDayNamesPanel() : JPanel
	{
		_dayNamePanel = new JPanel( new GridLayout( 1, 7, 5, 5 ) );
		
		for( var i : Number = 0; i < 7; i++ )
		{
			var label : JLabel = new JLabel( ".", JLabel.CENTER );
			label.setFont( ASFont.getASFontFromASTextFormat( _daysLabelFormat ) );
			
			_dayNamePanel.append( label );
		}
		
		return _dayNamePanel;
	}
	
	private function _fillDayNamesPanel() : Void
	{
		for( var i : Number = 0; i < 7; i++ )
		{
			var label : JLabel = JLabel( _dayNamePanel.getComponent( i ) );
			var d : Number = ( i + _firstDayOfWeek ) % 7;
			
			label.setText( String( _dayNames[ d ] ).charAt( 0 ).toUpperCase() );	
		}
		
		_monthLabel.setText( _monthNames[ _currentMonth ] );
		_monthLabel.revalidateIfNecessary();
	}
	
	private function _createDayGrid() : JPanel
	{
		_dayGrid = new JPanel( new GridLayout( 6, 7, 5, 5 ) );		
		_gridLength = 42;
		
		for( var i : Number = 0; i < _gridLength; i++ )
		{
			var cell : FvDayCell = new FvDayCell( "", JLabel.CENTER );
			cell.setVerticalAlignment( JLabel.CENTER );
			cell.addEventListener( FvDayCell.ON_ROLLOVER, _onCellRollOver, this );			cell.addEventListener( FvDayCell.ON_ROLLOUT, _onCellRollOut, this );			cell.addEventListener( FvDayCell.ON_RELEASE, _onCellRollRelease, this );			cell.addEventListener( FvDayCell.ON_RELEASEOUTSIDE, _onCellRollOut, this );
			_dayGrid.append( cell );
		}
		
		return _dayGrid;
	}
	
	private function _fillDayCellGrid( n : Number, offset : Number ) : Void
	{
		var rest : Number = _gridLength - n - offset;
		var cpt : Number = 1;
		var days : Number = n + offset;
		
		for( var i : Number = 0; i < offset; i++ ) _fillGridCell( i );	
		for( var i : Number = offset; i < days; i++ ) _fillGridCell( i, cpt++ );	
		for( var i : Number = offset + n; i < _gridLength; i++ ) _fillGridCell( i );
	}
	
	private function _fillGridCell( index : Number, value : Number ) : Void
	{
		var cell : FvDayCell = FvDayCell( _dayGrid.getComponent( index ) );
		cell.setText( value.toString() );
		
		if( value )
		{
			if( _isValidDateField( value ) )
			{
				cell.setEnabled( true );
				cell.setSelected( _isCurrentCellMustBeSelected( value ) );
			}
			else
			{
				cell.setEnabled( false );
				cell.setSelected( false );
			}
			
			if( _showToday && _isToday( value ) )
			{
				cell.setBorder( new LineBorder( null, ASColor.BLACK, 1 ) );
			} 
			else cell.setBorder( null );
		}
		else
		{
			cell.disabled();
			cell.setBorder( null );
		}
	}
	
	private function _isValidDateField( value : Number ) : Boolean
	{
		var result : Boolean = true;
		
		if( _currentYear == _minYear && _currentMonth == _minMonth )
		{
			result = ( value >= DateUtils.getCurrentDate() );
			
			if( !_enableWeekEnd  && _isWeekEndDate( value ) ) result = false;
		}
		else if( !_enableWeekEnd ) result = !_isWeekEndDate( value );	
		
		if( _enableLockDays && _lockedDays.containsKey( _currentYear ) )
		{
			var a : Array = _lockedDays.get( _currentYear );
			var l : Number = a.length;
			
			for( var i : Number = 0; i < l; i++ )
			{
				var ld : FvLockDay = a[ i ];
				if( ld.isEqualDayAndMonth( value, _currentMonth + 1 ) ) return false;	
			}
		}
		
		if( _disabledDays[ value ] == true ) result = false;
		
		return result;
	}
	
	private function _isWeekEndDate( value : Number ) : Boolean
	{
		var d : Date = new Date( _currentYear, _currentMonth, value );
		var day : Number = d.getDay();
		
		return ( day == 0 || day == 6 );	
	}
	
	private function _getNumberOfDaysInMonth( year : Number, month : Number ) : Number
    {
        var n : Number;

        if ( month == 1 )
        {
            if ( ( ( year % 4 == 0 ) && ( year % 100 != 0 ) ) || ( year % 400 == 0 ) )
                n = 29;
            else n = 28;
        }
        else if ( month == 3 || month == 5 || month == 8 || month == 10 )
            n = 30;
        else n = 31;
		
        return n;
    }
	
	private function _getOffsetOfMonth( year : Number, month : Number ) : Number
    {
        var first : Date = new Date( year, month, 1 );
        var offset : Number = first.getDay() - _firstDayOfWeek;
        return offset < 0 ? offset + 7 : offset;
    }
    
    private function _onCellRollOver( source : FvDayCell ) : Void
	{
		if( !source.isSelected() ) source.highlight();	
	}
	
	private function _onCellRollOut( source : FvDayCell ) : Void
	{
		if( !source.isSelected() ) source.normal();
	}
	
	private function _onCellRollRelease( source : FvDayCell ) : Void
	{
		if( !_multiSelection )
			_resetAllCellsExceptCurrent( source );
		
		source.invert();
		_storeCurrentSelection();
		
		_fireEvent( onCalendarClickEVENT );
	}
	
	private function _resetAllCellsExceptCurrent( compare : FvDayCell ) : Void
	{
		for( var i : Number = 0; i < _gridLength; i++ )
		{
			var cell : FvDayCell = FvDayCell( _dayGrid.getComponent( i ) );
			if( cell != compare ) cell.normal();	
		}
	}
    
    private function _handleNavigationType( source : FvLabelButton, clickCount : Number, navigateThrowYear : Boolean ) : Void
    {
    	if( clickCount == 2 )
    	{
    		if( _yearNavigation ) 
    		{
    			if( !_enabledYearNavigation ) return;
    			
    			_inputYearComponent.setMinimum( _minYear );
    			_inputYearComponent.setMaximum( _maxYear );
    			_inputYearComponent.setValue( _currentYear );
    			
    			 FvInputComponentDialog.show( 
    			 	_inputYearComponent, 
    			 	FvCalendarResources.getInstance().yearSelection, 
    			 	this, 
    			 	_onYearInputResult
    			 );
    		}
    		else 
    		{
    			if( !_enabledMonthNavigation ) return;
    			
    			var a : Array;
    			
    			if( !_enablePastNavigation && ( _currentYear == _minYear ) )
    			{
    			 	a = ArrayUtil.clone( _monthNames ).slice( _minMonth, 12 );
    			}
    			else
    			{
    				a = ArrayUtil.clone( _monthNames );
    			}	
    			
    			_inputMonthComponent.setListData( a );
    			_inputMonthComponent.setValue( _monthNames[ _currentMonth ] );
    			
    			FvInputComponentDialog.show( 
    				_inputMonthComponent, 
    				FvCalendarResources.getInstance().monthSelection, 
    				this, 
    				_onMonthInputResult 
    			);
    		} 
    	}
    	else
    	{
	    	_yearNavigation = ( _enabledYearNavigation ) ? navigateThrowYear : false;
	    	_evaluateNavigationItem();
    	}
    }
    
    private function _onMonthInputResult( result ) : Void
    {
    	if( !_enablePastNavigation && ( _currentYear == _minYear ) )
		{
    		result += _minMonth;
		}
		
    	if( result != _currentMonth )
    		_gotoDate( 0, result - _currentMonth );
    }
    
    private function _onYearInputResult( result : Number ) : Void
    {
    	if( result != _currentYear )
    		_gotoDate( result - _currentYear, 0 );
    }
    
	private function _handleNextClick( source : FvLabelButton ) : Void
	{
		if( _yearNavigation ) _gotoNextYear();
		else _gotoNextMonth();	
	}
	
	private function _handlePreviousClick( source : FvLabelButton ) : Void
	{
		if( _yearNavigation ) _gotoPreviousYear();
		else _gotoPreviousMonth();	
	}
	
    private function _gotoNextYear() : Void
	{
		if( _currentYear + 1 <= _maxYear ) _gotoDate( 1, 0 );
	}
	
	private function _gotoPreviousYear() : Void
	{
		if( _currentYear - 1 >= _minYear ) _gotoDate( -1, 0 );	
	}
	
	private function _gotoNextMonth() : Void
	{
		if ( ( _currentYear + 1 > _maxYear ) && ( _currentMonth == 11 ) ) return;
		
		_gotoDate( 0, 1 );
	}
	
	private function _gotoPreviousMonth() : Void
	{
		if ( ( _currentYear - 1 < _minYear ) && ( _currentMonth - 1 < _minMonth ) ) return;
    	
		_gotoDate( 0, -1 );
	}
	
	private function _gotoDate( yearStep : Number, monthStep : Number ) : Void
	{
		_storeCurrentSelection();
				
		var o : Object = _retreiveCorrectMonthAndYear( yearStep, monthStep );
        _currentMonth = o.month;
        _currentYear = o.year;
        
        _fireEvent( onCalendarChangeViewEVENT );
        
		_updateCalendar();	
	}
	
	private function _storeCurrentSelection() : Void
	{
		var list : Array = new Array();
		
		for( var i : Number = 0; i < _gridLength; i++ )
		{
			var cell : FvDayCell = FvDayCell( _dayGrid.getComponent( i ) );
			if( cell.isSelected() ) list.push( Number( cell.getText() ) );
		}
		
		if( _multiSelection )
			_storedSelection[ _getIdentifier() ] = list.concat();
		else 
			_storedSelection = list[0];
			
		_fireEvent( onCalendarSelectionEVENT );
	}
	
	private function _isCurrentCellMustBeSelected( day : Number ) : Boolean
	{
		if( _multiSelection )
		{
			var list : Array = _storedSelection[ _getIdentifier() ];
			if( ArrayUtils.indexInArray( list, day ) > -1 ) return true;
			return false;
		}
		else
		{
			return ( day == _storedSelection );
		}
	}
	
	
	private function _isToday( value ) : Boolean
	{
		var today : Date = new Date();
		
		if ( 
			( today.getFullYear() == _currentYear ) && 
			( today.getMonth() == _currentMonth ) &&
			( today.getDate() == value )
		) return true;
		return false;
	}
	
	private function _getIdentifier() : String
	{
		return ( _currentYear + '/' + _currentMonth );	
	}
	
	private function _buildDateObject( value ) : Date
	{
		if( value == undefined ) return null;
		
		var date : Date = new Date( _currentYear, _currentMonth, value );
		return date;
	}
	
	private function _buildDateRangeObject( stored : Object ) : Array
	{
		var result : Array = new Array();
		
		for( var s : String in stored )
		{
			var a : Array = s.split( '/' );
			var y : Number = Number( a[0] );			var m : Number = Number( a[1] );
			var list : Array = stored[s];
			var l : Number = list.length;
			
			for( var i : Number = 0; i < l; i++ )
				result.push( new Date( y, m, list[i] ) );	
		}
		
		return result;
	}
	
	private function _retreiveCorrectMonthAndYear( yearStep : Number, monthStep : Number ) : Object
	{
		var oldYear : Number = _currentYear;
        var oldMonth : Number = _currentMonth;
        var newYear : Number = oldYear + yearStep;
        var newMonth : Number = oldMonth + monthStep;
		
        while ( newMonth < 0 )
        {
            newYear--;
            newMonth += 12;
        }
		
        while ( newMonth > 11 )
        {
            newYear++;
            newMonth -= 12;
        }
    	
    	var result : Object = new Object();
    	result.month = newMonth;
    	result.year = newYear;
    	
    	return result;    	
	}
	
	private function _evaluateNavigationItem() : Void
	{
		if( _yearNavigation && _enabledYearNavigation )
		{
			if( _currentYear + 1 <= _maxYear )
			{
				_fwdButton.setEnabled( true );
				_fwdButton.setToolTipText( String( _currentYear + 1 ) );
			}
			else _fwdButton.setEnabled( false );
			
			if( _currentYear - 1 >= _minYear )
			{
				_backButton.setEnabled( true );
				_backButton.setToolTipText( String( _currentYear - 1 ) );
			}
			else _backButton.setEnabled( false );
		}
		else if( _enabledMonthNavigation )
		{
			var o : Object;
			
			o = _retreiveCorrectMonthAndYear( 0, 1 );
			if( o.year <= _maxYear )
			{
				_fwdButton.setEnabled( true );
				_fwdButton.setToolTipText( String( _monthNames[ o.month ] ) );	
			}
			else _fwdButton.setEnabled( false );
			
			o = _retreiveCorrectMonthAndYear( 0, -1 );
			if( o.year >= _minYear && o.month >= _minMonth )
			{
				_backButton.setEnabled( true );
				_backButton.setToolTipText( String( _monthNames[ o.month ] ) );	
			}
			else _backButton.setEnabled( false );
		}
	}
	
	private function _isValidDate( date : Date ) : Boolean
	{
		if( !date ) return false;
		
		var year : Number = date.getFullYear();
		if( year >= _minYear && year <= _maxYear )
		{
			return true;
		}
		return false;
	}
	
	private function _fireEvent( type : EventType ) : Void
	{
		dispatchEvent( new Event( this, type, [ type ] ) );
	}
}